package org.consistenthash;

import java.util.SortedMap;
import java.util.TreeMap;

/**
 * @author zyy
 * @date 2021/1/12 下午6:03
 * @description 一致性Hash算法
 *
 *
 *首先有一条直线，直线开头和结尾分别定为为1和2的32次方减1，这相当于一个地址，
 *对于这样一条 线，弯过来构成一个圆环形成闭环，这样的一个圆环称为hash环。
 * 我们把服务器的ip或者主机名求 hash值然后对应到hash环上，那么针对客户端用户，
 * 也根据它的ip进行hash求值，对应到环上某个位 置，然后如何确定一个客户端路由到
 * 哪个服务器处理呢?按照顺时针方向找最近的服务器节点
 *
 * 假如将服务器3下线，服务器3下线后，原来路由到3的客户端重新路由到服务器4，
 * 对于其他客户端没有 影响只是这一小部分受影响(请求的迁移达到了最小，
 * 这样的算法对分布式集群来说非常合适的，避免 了大量请求迁移 )
 *
 * 增加服务器5之后，原来路由到3的部分客户端路由到新增服务器5上，
 * 对于其他客户端没有影响只是这 一小部分受影响(请求的迁移达到了最小，
 * 这样的算法对分布式集群来说非常合适的，避免了大量请求 迁移 )
 *
 * 1)如前所述，每一台服务器负责一段，一致性哈希算法对于节点的增减都只需重定位环空间中的一小 部分数据，具有较好的容错性和可扩展性。
 * 但是，一致性哈希算法在服务节点太少时，容易因为节点分部不均匀而造成数据倾斜问题。例如系统中 只有两台服务器，其环分布如下，节点2只能负责非常小的一段，大量的客户端
 * 请求落在了节点1上，这就是数据(请求)倾斜问题 2)为了解决这种数据倾斜问题，一致性哈希算法引入了虚拟节点机制，即对每一个服务节点计算多个
 * 哈希，每个计算结果位置都放置一个此服务节点，称为虚拟节点。
 * 具体做法可以在服务器ip或主机名的后面增加编号来实现。比如，可以为每台服务器计算三个虚拟节 点，
 * 于是可以分别计算 “节点1的ip#1”、“节点1的ip#2”、“节点1的ip#3”、“节点2的ip#1”、“节点2的 ip#2”、“节点2的ip#3”的哈希值，
 * 于是形成六个虚拟节点，当客户端被路由到虚拟节点的时候其实是被 路由到该虚拟节点所对应的真实节点
 *
 */
public class ConsistentHashWithVirtual {

    public static void main(String[] args) {
        //step1 初始化：把服务器ip的哈希值对应到哈希环上
        //定义服务器IP
        String[] tomcatServers = new String[]{"123.111.0.0","123.101.3.1","111.20.35.2","123.98.26.3"};

        SortedMap<Integer,String> hashServerMap =  new TreeMap<>();

        // 定义针对每个真实服务器虚拟出来几个节点
        int virtaulCount = 3;

        for (String tomcatServer : tomcatServers) {
            //求出每一个Ip对应的 Hash值
            int serverHash = Math.abs(tomcatServer.hashCode());
            //存储Hash值和IP的对应关系
            hashServerMap.put(serverHash,tomcatServer);

            // 处理虚拟节点
            for(int i = 0; i < virtaulCount; i++) {
                int virtualHash = Math.abs((tomcatServer + "#" + i).hashCode());
                hashServerMap.put(virtualHash,"----由虚拟节点"+ i  + "映射过来的请求："+ tomcatServer);
            }
        }

        //step2 针对客户端IP求出hash值
        // 定义客户端IP
        String[] clients = new String[]{"10.78.12.3","113.25.63.1","126.12.3.8"};
        for (String client : clients) {
            int clientHash = Math.abs(client.hashCode());
            //step3 针对客户端 找到能够处理当前客户端请求的服务器（hash环上顺时针最近）
            //根据客户端ip的hash值去找出哪一个服务器节点能够处理
            SortedMap<Integer, String> integerStringSortedMap = hashServerMap.tailMap(clientHash);
            if(integerStringSortedMap.isEmpty()){
                //取hash环上顺时针第一条数据
                Integer firstKey = hashServerMap.firstKey();
                System.out.println("==========>>>>客户端：" + client + " 被路由到服务器：" + hashServerMap.get(firstKey));
            }else {
                Integer firstKey = integerStringSortedMap.firstKey();
                System.out.println("==========>>>>客户端：" + client + " 被路由到服务器：" + hashServerMap.get(firstKey));
            }

        }

    }

}
